#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include "ths_xml.h"
#include "ths_test.h"

static _Bool string_equals(struct xml_string* a, char const* b) {
	size_t a_length = xml_string_length(a);
	size_t b_length = strlen(b);

	uint8_t* a_buffer = malloc((a_length + 1) * sizeof(uint8_t));
	xml_string_copy(a, a_buffer, a_length);
	a_buffer[a_length] = 0;

	if (a_length != b_length) {
		fprintf(stderr, "string_equals: %s#%i <> %s#%i\n", a_buffer, (int)a_length, b, (int)b_length);
		return false;
	}

	size_t i = 0; for (; i < a_length; ++i) {
		if (a_buffer[i] != b[i]) {
			fprintf(stderr, "string_equals: %s <> %s\n", a_buffer, b);
			return false;
		}
	}

	return true;
}


static void ths_xml_example() {

	/* XML source, could be read from disk
	 */
	uint8_t* source = ""
		"<Root>"
			"<Hello>World</Hello>"
			"<This>"
				"<Is>:-)</Is>"
				"<An>:-O</An>"
				"<Example>:-D</Example>"
			"</This>"
		"</Root>"
	;


	/* Parse the document
	 *
	 * Watch out: Remember not to free the source until you have freed the
	 *     document itself. If you have to free the source before, supply a
	 *     copy to xml_parse_document which can be freed together with the
	 *     document (`free_buffer' argument to `xml_document_free')
	 */
	struct xml_document* document = xml_parse_document(source, strlen(source));

	/* You _have_ to check the result of `xml_parse_document', if it's 0
	 * then the source could not be parsed. If you think this is a bug in
	 * xml.c, than use a debug build (cmake -DCMAKE_BUILD_TYPE=Debug) which
	 * will verbosely tell you about the parsing process
	 */
	if (!document) {
		printf("Could parse document\n");
		exit(EXIT_FAILURE);
	}
	struct xml_node* root = xml_document_root(document);


	/* Say Hello World :-)
	 */
	struct xml_node* root_hello = xml_node_child(root, 0);
	struct xml_string* hello = xml_node_name(root_hello);
	struct xml_string* world = xml_node_content(root_hello);

	/* Watch out: `xml_string_copy' will not 0-terminate your buffers! (but
	 *     `calloc' will :-)
	 */
	uint8_t* hello_0 = calloc(xml_string_length(hello) + 1, sizeof(uint8_t));
	uint8_t* world_0 = calloc(xml_string_length(world) + 1, sizeof(uint8_t));
	xml_string_copy(hello, hello_0, xml_string_length(hello));
	xml_string_copy(world, world_0, xml_string_length(world));

	printf("%s %s\n", hello_0, world_0);
	free(hello_0);
	free(world_0);


	/* Extract amount of Root/This children
	 */
	struct xml_node* root_this = xml_node_child(root, 1);
	printf("Root/This has %lu children\n", (unsigned long)xml_node_children(root_this));


	/* Remember to free the document or you'll risk a memory leak
	 */
	xml_document_free(document, false);
}

/**
 * Tries to parse a simple document containing only one tag
 */
static void test_xml_parse_document_0() {
	SOURCE(source, "<Hello>World</Hello>");
//	uint8_t* source = malloc((1 + strlen("<Hello>World</Hello>")) * sizeof(uint8_t));
//	{	char const* content_string = "<Hello>World</Hello>";
//		memcpy(source, content_string, strlen("<Hello>World</Hello>") + 1);
//	}

	struct xml_document* document = xml_parse_document(source, strlen(source));
	assert_that(document, "Could not parse document");

	struct xml_node* root = xml_document_root(document);
	assert_that(string_equals(xml_node_name(root), "Hello"), "root node name must be `Hello'");
	assert_that(string_equals(xml_node_content(root), "World"), "root node content must be `World'");

	xml_document_free(document, true);
}

/**
 * Tries to parse a document containing multiple tags
 */
static void test_xml_parse_document_1() {
	SOURCE(source, ""
		"<Parent>\n"
		"\t<Child>\n"
		"\t\tFirst content\n"
		"\t</Child>\n"
		"\t<Child>\n"
		"\t\tSecond content\n"
		"\t</Child>\n"
		"</Parent>\n"
	);
	struct xml_document* document = xml_parse_document(source, strlen(source));
	assert_that(document, "Could not parse document");

	struct xml_node* root = xml_document_root(document);
	assert_that(string_equals(xml_node_name(root), "Parent"), "root node name must be `Parent'");
	assert_that(2 == xml_node_children(root), "root must have two children");

	struct xml_node* first_child = xml_node_child(root, 0);
	struct xml_node* second_child = xml_node_child(root, 1);
	assert_that(first_child && second_child, "Failed retrieving the children of root");

	struct xml_node* third_child = xml_node_child(root, 2);
	assert_that(!third_child, "root has a third child where non should be");

	assert_that(string_equals(xml_node_name(first_child), "Child"), "first_child node name must be `Child'");
	assert_that(string_equals(xml_node_content(first_child), "First content"), "first_child node content must be `First content'");
	assert_that(string_equals(xml_node_name(second_child), "Child"), "second_child node name must be `Child'");
	assert_that(string_equals(xml_node_content(second_child), "Second content"), "second_child node content must be `tSecond content'");

	xml_document_free(document, true);
}



/**
 * Tests the eas functionality
 */
static void test_xml_parse_document_2() {
	SOURCE(source, ""
		"<Parent>\n"
		"\t<Child>\n"
		"\t\tFirst content\n"
		"\t</Child>\n"
		"\t<This><Is>\n"
			"<A><Test>Content A</Test></A>\n"
			"<B><Test>Content B</Test></B>\n"
		"\t</Is></This>\n"
		"\t<Child>\n"
		"\t\tSecond content\n"
		"\t</Child>\n"
		"</Parent>\n"
	);
	struct xml_document* document = xml_parse_document(source, strlen(source));
	assert_that(document, "Could not parse document");

	struct xml_node* root = xml_document_root(document);
	assert_that(string_equals(xml_node_name(root), "Parent"), "root node name must be `Parent'");
	assert_that(3 == xml_node_children(root), "root must have two children");

	struct xml_node* test_a = xml_easy_child(root, "This", "Is", "A", "Test", 0);
	assert_that(test_a, "Cannot find Parent/This/Is/A/Test");
	assert_that(string_equals(xml_node_content(test_a), "Content A"), "Content of Parent/This/Is/A/Test must be `Content A'");

	struct xml_node* test_b = xml_easy_child(root, "This", "Is", "B", "Test", 0);
	assert_that(test_b, "Cannot find Parent/This/Is/B/Test");
	assert_that(string_equals(xml_node_content(test_b), "Content B"), "Content of Parent/This/Is/B/Test must be `Content B'");

	struct xml_node* test_c = xml_easy_child(root, "This", "Is", "C", "Test", 0);
	assert_that(!test_c, "Must not find Parent/This/Is/C/Test because no such path exists");

	struct xml_node* must_be_null = xml_easy_child(root, "Child");
	assert_that(!must_be_null, "Parent/Child cannot be a valid expression, because there are two children named `Child' in `Parent'");

	uint8_t* name_is = xml_easy_name(xml_easy_child(root, "This", "Is", 0));
	assert_that(!strcmp(name_is, "Is"), "Name of Parent/This/Is must be `Is'");
	free(name_is);

	uint8_t* content_a = xml_easy_content(test_a);
	assert_that(!strcmp(content_a, "Content A"), "Content of Parent/This/Is/A/Test must be `Content A'");
	free(content_a);

	xml_document_free(document, true);
}



/**
 * Tests the xml_open_document functionality
 */
static void test_xml_parse_document_3() {
	#define FILE_NAME "./test/data/xml/test.xml"
	FILE* handle = fopen(FILE_NAME, "rb");
	assert_that(handle, "Cannot open " FILE_NAME);

	struct xml_document* document = xml_open_document(handle);
	assert_that(document, "Cannot parse " FILE_NAME);

	struct xml_node* element = xml_easy_child(xml_document_root(document), "Element", "With", 0);
	assert_that(element, "Cannot find Document/Element/With");
	assert_that(string_equals(xml_node_content(element), "Child"), "Content of Document/Element/With must be `Child'");

	xml_document_free(document, true);
	#undef FILE_NAME
}



void ths_xml_test_all() {
	ths_xml_example();

	test_xml_parse_document_0();
	test_xml_parse_document_1();
	test_xml_parse_document_2();
	test_xml_parse_document_3();

	fprintf(stdout, "All tests passed :-)\n");
}
